home *** CD-ROM | disk | FTP | other *** search
/ PC go! 2018 January / PCgo 01-2018 CD-ROM Germany.iso / nw.pak / Unnamed File 004895.txt < prev    next >
Encoding:
Text File  |  2015-07-29  |  17.1 KB  |  526 lines

  1. // Copyright 2014 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4.  
  5. // -----------------------------------------------------------------------------
  6. // NOTE: If you change this file you need to touch
  7. // extension_renderer_resources.grd to have your change take effect.
  8. // -----------------------------------------------------------------------------
  9.  
  10. //==============================================================================
  11. // This file contains a class that implements a subset of JSON Schema.
  12. // See: http://www.json.com/json-schema-proposal/ for more details.
  13. //
  14. // The following features of JSON Schema are not implemented:
  15. // - requires
  16. // - unique
  17. // - disallow
  18. // - union types (but replaced with 'choices')
  19. //
  20. // The following properties are not applicable to the interface exposed by
  21. // this class:
  22. // - options
  23. // - readonly
  24. // - title
  25. // - description
  26. // - format
  27. // - default
  28. // - transient
  29. // - hidden
  30. //
  31. // There are also these departures from the JSON Schema proposal:
  32. // - function and undefined types are supported
  33. // - null counts as 'unspecified' for optional values
  34. // - added the 'choices' property, to allow specifying a list of possible types
  35. //   for a value
  36. // - by default an "object" typed schema does not allow additional properties.
  37. //   if present, "additionalProperties" is to be a schema against which all
  38. //   additional properties will be validated.
  39. //==============================================================================
  40.  
  41. var loadTypeSchema = require('utils').loadTypeSchema;
  42. var CHECK = requireNative('logging').CHECK;
  43.  
  44. function isInstanceOfClass(instance, className) {
  45.   while ((instance = instance.__proto__)) {
  46.     if (instance.constructor.name == className)
  47.       return true;
  48.   }
  49.   return false;
  50. }
  51.  
  52. function isOptionalValue(value) {
  53.   return typeof(value) === 'undefined' || value === null;
  54. }
  55.  
  56. function enumToString(enumValue) {
  57.   if (enumValue.name === undefined)
  58.     return enumValue;
  59.  
  60.   return enumValue.name;
  61. }
  62.  
  63. /**
  64.  * Validates an instance against a schema and accumulates errors. Usage:
  65.  *
  66.  * var validator = new JSONSchemaValidator();
  67.  * validator.validate(inst, schema);
  68.  * if (validator.errors.length == 0)
  69.  *   console.log("Valid!");
  70.  * else
  71.  *   console.log(validator.errors);
  72.  *
  73.  * The errors property contains a list of objects. Each object has two
  74.  * properties: "path" and "message". The "path" property contains the path to
  75.  * the key that had the problem, and the "message" property contains a sentence
  76.  * describing the error.
  77.  */
  78. function JSONSchemaValidator() {
  79.   this.errors = [];
  80.   this.types = [];
  81. }
  82.  
  83. JSONSchemaValidator.messages = {
  84.   invalidEnum: "Value must be one of: [*].",
  85.   propertyRequired: "Property is required.",
  86.   unexpectedProperty: "Unexpected property.",
  87.   arrayMinItems: "Array must have at least * items.",
  88.   arrayMaxItems: "Array must not have more than * items.",
  89.   itemRequired: "Item is required.",
  90.   stringMinLength: "String must be at least * characters long.",
  91.   stringMaxLength: "String must not be more than * characters long.",
  92.   stringPattern: "String must match the pattern: *.",
  93.   numberFiniteNotNan: "Value must not be *.",
  94.   numberMinValue: "Value must not be less than *.",
  95.   numberMaxValue: "Value must not be greater than *.",
  96.   numberIntValue: "Value must fit in a 32-bit signed integer.",
  97.   numberMaxDecimal: "Value must not have more than * decimal places.",
  98.   invalidType: "Expected '*' but got '*'.",
  99.   invalidTypeIntegerNumber:
  100.       "Expected 'integer' but got 'number', consider using Math.round().",
  101.   invalidChoice: "Value does not match any valid type choices.",
  102.   invalidPropertyType: "Missing property type.",
  103.   schemaRequired: "Schema value required.",
  104.   unknownSchemaReference: "Unknown schema reference: *.",
  105.   notInstance: "Object must be an instance of *."
  106. };
  107.  
  108. /**
  109.  * Builds an error message. Key is the property in the |errors| object, and
  110.  * |opt_replacements| is an array of values to replace "*" characters with.
  111.  */
  112. JSONSchemaValidator.formatError = function(key, opt_replacements) {
  113.   var message = this.messages[key];
  114.   if (opt_replacements) {
  115.     for (var i = 0; i < opt_replacements.length; i++) {
  116.       message = message.replace("*", opt_replacements[i]);
  117.     }
  118.   }
  119.   return message;
  120. };
  121.  
  122. /**
  123.  * Classifies a value as one of the JSON schema primitive types. Note that we
  124.  * don't explicitly disallow 'function', because we want to allow functions in
  125.  * the input values.
  126.  */
  127. JSONSchemaValidator.getType = function(value) {
  128.   var s = typeof value;
  129.  
  130.   if (s == "object") {
  131.     if (value === null) {
  132.       return "null";
  133.     } else if (Object.prototype.toString.call(value) == "[object Array]") {
  134.       return "array";
  135.     } else if (Object.prototype.toString.call(value) ==
  136.                "[object ArrayBuffer]") {
  137.       return "binary";
  138.     }
  139.   } else if (s == "number") {
  140.     if (value % 1 == 0) {
  141.       return "integer";
  142.     }
  143.   }
  144.  
  145.   return s;
  146. };
  147.  
  148. /**
  149.  * Add types that may be referenced by validated schemas that reference them
  150.  * with "$ref": <typeId>. Each type must be a valid schema and define an
  151.  * "id" property.
  152.  */
  153. JSONSchemaValidator.prototype.addTypes = function(typeOrTypeList) {
  154.   function addType(validator, type) {
  155.     if (!type.id)
  156.       throw new Error("Attempt to addType with missing 'id' property");
  157.     validator.types[type.id] = type;
  158.   }
  159.  
  160.   if (typeOrTypeList instanceof Array) {
  161.     for (var i = 0; i < typeOrTypeList.length; i++) {
  162.       addType(this, typeOrTypeList[i]);
  163.     }
  164.   } else {
  165.     addType(this, typeOrTypeList);
  166.   }
  167. }
  168.  
  169. /**
  170.  * Returns a list of strings of the types that this schema accepts.
  171.  */
  172. JSONSchemaValidator.prototype.getAllTypesForSchema = function(schema) {
  173.   var schemaTypes = [];
  174.   if (schema.type)
  175.     $Array.push(schemaTypes, schema.type);
  176.   if (schema.choices) {
  177.     for (var i = 0; i < schema.choices.length; i++) {
  178.       var choiceTypes = this.getAllTypesForSchema(schema.choices[i]);
  179.       schemaTypes = $Array.concat(schemaTypes, choiceTypes);
  180.     }
  181.   }
  182.   var ref = schema['$ref'];
  183.   if (ref) {
  184.     var type = this.getOrAddType(ref);
  185.     CHECK(type, 'Could not find type ' + ref);
  186.     schemaTypes = $Array.concat(schemaTypes, this.getAllTypesForSchema(type));
  187.   }
  188.   return schemaTypes;
  189. };
  190.  
  191. JSONSchemaValidator.prototype.getOrAddType = function(typeName) {
  192.   if (!this.types[typeName])
  193.     this.types[typeName] = loadTypeSchema(typeName);
  194.   return this.types[typeName];
  195. };
  196.  
  197. /**
  198.  * Returns true if |schema| would accept an argument of type |type|.
  199.  */
  200. JSONSchemaValidator.prototype.isValidSchemaType = function(type, schema) {
  201.   if (type == 'any')
  202.     return true;
  203.  
  204.   // TODO(kalman): I don't understand this code. How can type be "null"?
  205.   if (schema.optional && (type == "null" || type == "undefined"))
  206.     return true;
  207.  
  208.   var schemaTypes = this.getAllTypesForSchema(schema);
  209.   for (var i = 0; i < schemaTypes.length; i++) {
  210.     if (schemaTypes[i] == "any" || type == schemaTypes[i] ||
  211.         (type == "integer" && schemaTypes[i] == "number"))
  212.       return true;
  213.   }
  214.  
  215.   return false;
  216. };
  217.  
  218. /**
  219.  * Returns true if there is a non-null argument that both |schema1| and
  220.  * |schema2| would accept.
  221.  */
  222. JSONSchemaValidator.prototype.checkSchemaOverlap = function(schema1, schema2) {
  223.   var schema1Types = this.getAllTypesForSchema(schema1);
  224.   for (var i = 0; i < schema1Types.length; i++) {
  225.     if (this.isValidSchemaType(schema1Types[i], schema2))
  226.       return true;
  227.   }
  228.   return false;
  229. };
  230.  
  231. /**
  232.  * Validates an instance against a schema. The instance can be any JavaScript
  233.  * value and will be validated recursively. When this method returns, the
  234.  * |errors| property will contain a list of errors, if any.
  235.  */
  236. JSONSchemaValidator.prototype.validate = function(instance, schema, opt_path) {
  237.   var path = opt_path || "";
  238.  
  239.   if (!schema) {
  240.     this.addError(path, "schemaRequired");
  241.     return;
  242.   }
  243.  
  244.   // If this schema defines itself as reference type, save it in this.types.
  245.   if (schema.id)
  246.     this.types[schema.id] = schema;
  247.  
  248.   // If the schema has an extends property, the instance must validate against
  249.   // that schema too.
  250.   if (schema.extends)
  251.     this.validate(instance, schema.extends, path);
  252.  
  253.   // If the schema has a $ref property, the instance must validate against
  254.   // that schema too. It must be present in this.types to be referenced.
  255.   var ref = schema["$ref"];
  256.   if (ref) {
  257.     if (!this.getOrAddType(ref))
  258.       this.addError(path, "unknownSchemaReference", [ ref ]);
  259.     else
  260.       this.validate(instance, this.getOrAddType(ref), path)
  261.   }
  262.  
  263.   // If the schema has a choices property, the instance must validate against at
  264.   // least one of the items in that array.
  265.   if (schema.choices) {
  266.     this.validateChoices(instance, schema, path);
  267.     return;
  268.   }
  269.  
  270.   // If the schema has an enum property, the instance must be one of those
  271.   // values.
  272.   if (schema.enum) {
  273.     if (!this.validateEnum(instance, schema, path))
  274.       return;
  275.   }
  276.  
  277.   if (schema.type && schema.type != "any") {
  278.     if (!this.validateType(instance, schema, path))
  279.       return;
  280.  
  281.     // Type-specific validation.
  282.     switch (schema.type) {
  283.       case "object":
  284.         this.validateObject(instance, schema, path);
  285.         break;
  286.       case "array":
  287.         this.validateArray(instance, schema, path);
  288.         break;
  289.       case "string":
  290.         this.validateString(instance, schema, path);
  291.         break;
  292.       case "number":
  293.       case "integer":
  294.         this.validateNumber(instance, schema, path);
  295.         break;
  296.     }
  297.   }
  298. };
  299.  
  300. /**
  301.  * Validates an instance against a choices schema. The instance must match at
  302.  * least one of the provided choices.
  303.  */
  304. JSONSchemaValidator.prototype.validateChoices =
  305.     function(instance, schema, path) {
  306.   var originalErrors = this.errors;
  307.  
  308.   for (var i = 0; i < schema.choices.length; i++) {
  309.     this.errors = [];
  310.     this.validate(instance, schema.choices[i], path);
  311.     if (this.errors.length == 0) {
  312.       this.errors = originalErrors;
  313.       return;
  314.     }
  315.   }
  316.  
  317.   this.errors = originalErrors;
  318.   this.addError(path, "invalidChoice");
  319. };
  320.  
  321. /**
  322.  * Validates an instance against a schema with an enum type. Populates the
  323.  * |errors| property, and returns a boolean indicating whether the instance
  324.  * validates.
  325.  */
  326. JSONSchemaValidator.prototype.validateEnum = function(instance, schema, path) {
  327.   for (var i = 0; i < schema.enum.length; i++) {
  328.     if (instance === enumToString(schema.enum[i]))
  329.       return true;
  330.   }
  331.  
  332.   this.addError(path, "invalidEnum",
  333.                 [schema.enum.map(enumToString).join(", ")]);
  334.   return false;
  335. };
  336.  
  337. /**
  338.  * Validates an instance against an object schema and populates the errors
  339.  * property.
  340.  */
  341. JSONSchemaValidator.prototype.validateObject =
  342.     function(instance, schema, path) {
  343.   if (schema.properties) {
  344.     for (var prop in schema.properties) {
  345.       // It is common in JavaScript to add properties to Object.prototype. This
  346.       // check prevents such additions from being interpreted as required
  347.       // schema properties.
  348.       // TODO(aa): If it ever turns out that we actually want this to work,
  349.       // there are other checks we could put here, like requiring that schema
  350.       // properties be objects that have a 'type' property.
  351.       if (!$Object.hasOwnProperty(schema.properties, prop))
  352.         continue;
  353.  
  354.       var propPath = path ? path + "." + prop : prop;
  355.       if (schema.properties[prop] == undefined) {
  356.         this.addError(propPath, "invalidPropertyType");
  357.       } else if (prop in instance && !isOptionalValue(instance[prop])) {
  358.         this.validate(instance[prop], schema.properties[prop], propPath);
  359.       } else if (!schema.properties[prop].optional) {
  360.         this.addError(propPath, "propertyRequired");
  361.       }
  362.     }
  363.   }
  364.  
  365.   // If "instanceof" property is set, check that this object inherits from
  366.   // the specified constructor (function).
  367.   if (schema.isInstanceOf) {
  368.     if (!isInstanceOfClass(instance, schema.isInstanceOf))
  369.       this.addError(propPath, "notInstance", [schema.isInstanceOf]);
  370.   }
  371.  
  372.   // Exit early from additional property check if "type":"any" is defined.
  373.   if (schema.additionalProperties &&
  374.       schema.additionalProperties.type &&
  375.       schema.additionalProperties.type == "any") {
  376.     return;
  377.   }
  378.  
  379.   // By default, additional properties are not allowed on instance objects. This
  380.   // can be overridden by setting the additionalProperties property to a schema
  381.   // which any additional properties must validate against.
  382.   for (var prop in instance) {
  383.     if (schema.properties && prop in schema.properties)
  384.       continue;
  385.  
  386.     // Any properties inherited through the prototype are ignored.
  387.     if (!$Object.hasOwnProperty(instance, prop))
  388.       continue;
  389.  
  390.     var propPath = path ? path + "." + prop : prop;
  391.     if (schema.additionalProperties)
  392.       this.validate(instance[prop], schema.additionalProperties, propPath);
  393.     else
  394.       this.addError(propPath, "unexpectedProperty");
  395.   }
  396. };
  397.  
  398. /**
  399.  * Validates an instance against an array schema and populates the errors
  400.  * property.
  401.  */
  402. JSONSchemaValidator.prototype.validateArray = function(instance, schema, path) {
  403.   var typeOfItems = JSONSchemaValidator.getType(schema.items);
  404.  
  405.   if (typeOfItems == 'object') {
  406.     if (schema.minItems && instance.length < schema.minItems) {
  407.       this.addError(path, "arrayMinItems", [schema.minItems]);
  408.     }
  409.  
  410.     if (typeof schema.maxItems != "undefined" &&
  411.         instance.length > schema.maxItems) {
  412.       this.addError(path, "arrayMaxItems", [schema.maxItems]);
  413.     }
  414.  
  415.     // If the items property is a single schema, each item in the array must
  416.     // have that schema.
  417.     for (var i = 0; i < instance.length; i++) {
  418.       this.validate(instance[i], schema.items, path + "." + i);
  419.     }
  420.   } else if (typeOfItems == 'array') {
  421.     // If the items property is an array of schemas, each item in the array must
  422.     // validate against the corresponding schema.
  423.     for (var i = 0; i < schema.items.length; i++) {
  424.       var itemPath = path ? path + "." + i : String(i);
  425.       if (i in instance && !isOptionalValue(instance[i])) {
  426.         this.validate(instance[i], schema.items[i], itemPath);
  427.       } else if (!schema.items[i].optional) {
  428.         this.addError(itemPath, "itemRequired");
  429.       }
  430.     }
  431.  
  432.     if (schema.additionalProperties) {
  433.       for (var i = schema.items.length; i < instance.length; i++) {
  434.         var itemPath = path ? path + "." + i : String(i);
  435.         this.validate(instance[i], schema.additionalProperties, itemPath);
  436.       }
  437.     } else {
  438.       if (instance.length > schema.items.length) {
  439.         this.addError(path, "arrayMaxItems", [schema.items.length]);
  440.       }
  441.     }
  442.   }
  443. };
  444.  
  445. /**
  446.  * Validates a string and populates the errors property.
  447.  */
  448. JSONSchemaValidator.prototype.validateString =
  449.     function(instance, schema, path) {
  450.   if (schema.minLength && instance.length < schema.minLength)
  451.     this.addError(path, "stringMinLength", [schema.minLength]);
  452.  
  453.   if (schema.maxLength && instance.length > schema.maxLength)
  454.     this.addError(path, "stringMaxLength", [schema.maxLength]);
  455.  
  456.   if (schema.pattern && !schema.pattern.test(instance))
  457.     this.addError(path, "stringPattern", [schema.pattern]);
  458. };
  459.  
  460. /**
  461.  * Validates a number and populates the errors property. The instance is
  462.  * assumed to be a number.
  463.  */
  464. JSONSchemaValidator.prototype.validateNumber =
  465.     function(instance, schema, path) {
  466.   // Forbid NaN, +Infinity, and -Infinity.  Our APIs don't use them, and
  467.   // JSON serialization encodes them as 'null'.  Re-evaluate supporting
  468.   // them if we add an API that could reasonably take them as a parameter.
  469.   if (isNaN(instance) ||
  470.       instance == Number.POSITIVE_INFINITY ||
  471.       instance == Number.NEGATIVE_INFINITY )
  472.     this.addError(path, "numberFiniteNotNan", [instance]);
  473.  
  474.   if (schema.minimum !== undefined && instance < schema.minimum)
  475.     this.addError(path, "numberMinValue", [schema.minimum]);
  476.  
  477.   if (schema.maximum !== undefined && instance > schema.maximum)
  478.     this.addError(path, "numberMaxValue", [schema.maximum]);
  479.  
  480.   // Check for integer values outside of -2^31..2^31-1.
  481.   if (schema.type === "integer" && (instance | 0) !== instance)
  482.     this.addError(path, "numberIntValue", []);
  483.  
  484.   if (schema.maxDecimal && instance * Math.pow(10, schema.maxDecimal) % 1)
  485.     this.addError(path, "numberMaxDecimal", [schema.maxDecimal]);
  486. };
  487.  
  488. /**
  489.  * Validates the primitive type of an instance and populates the errors
  490.  * property. Returns true if the instance validates, false otherwise.
  491.  */
  492. JSONSchemaValidator.prototype.validateType = function(instance, schema, path) {
  493.   var actualType = JSONSchemaValidator.getType(instance);
  494.   if (schema.type == actualType ||
  495.       (schema.type == "number" && actualType == "integer")) {
  496.     return true;
  497.   } else if (schema.type == "integer" && actualType == "number") {
  498.     this.addError(path, "invalidTypeIntegerNumber");
  499.     return false;
  500.   } else {
  501.     this.addError(path, "invalidType", [schema.type, actualType]);
  502.     return false;
  503.   }
  504. };
  505.  
  506. /**
  507.  * Adds an error message. |key| is an index into the |messages| object.
  508.  * |replacements| is an array of values to replace '*' characters in the
  509.  * message.
  510.  */
  511. JSONSchemaValidator.prototype.addError = function(path, key, replacements) {
  512.   $Array.push(this.errors, {
  513.     path: path,
  514.     message: JSONSchemaValidator.formatError(key, replacements)
  515.   });
  516. };
  517.  
  518. /**
  519.  * Resets errors to an empty list so you can call 'validate' again.
  520.  */
  521. JSONSchemaValidator.prototype.resetErrors = function() {
  522.   this.errors = [];
  523. };
  524.  
  525. exports.JSONSchemaValidator = JSONSchemaValidator;
  526.